- 
          
- 
                Notifications
    You must be signed in to change notification settings 
- Fork 23.5k
          Implement a @meta annotation
          #111312
        
          New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
  
    Implement a @meta annotation
  
  #111312
              
            Conversation
…methods on Script
…notation->resolved_arguments
| Having the uid as a separate file, but at the same time the metadata as an annotation inside the script looks really confusing. | 
| In the attempt to solve godotengine/godot-proposals#1316 , I don't think it ever crossed most contributor's minds to utilize a single  | 
| Btw in your commits you ping user with  | 
| 
 Unlike UIDs, the keys/values here are user-defined at the class and class-member level, so I think the text of the script is an appropriate place for them. That said, I understand that calling this "metadata" could be confusing given that other kinds of script metadata (like UIDs) already exist and live elsewhere. The name was inspired by  | 
| 
 What application of custom annotations from the original proposal did you have in mind? I think this PR unblocks the original  | 
| +@TokageItLab , since you took some interest in PR #102516. | 
| 
 @Mickeon - to be more concrete, here's how  my_data_class.gd class_name MyDataClass
extends RefCounted
@meta("serialize", { name = "json_foo" })
var foo: int
@meta("serialize")
var bar: String
var x: boolmain.gd static func to_dict(data_object: RefCounted) -> Dictionary[StringName, Variant]:
    var result: Dictionary[StringName, Variant]
    var script = data_object.get_script() as Script
    if script == null: return result
    # Introspect @meta("serialize") annotations.
    for meta in script.get_script_meta("serialize"):
        if meta.target_type != Script.MetaTargetType.META_TARGET_VARIABLE:
            push_warning("ignoring @meta(\"serialize\") on non-property")
            continue
        var key: StringName =\
            meta.target_name if typeof(meta.value) == TYPE_BOOL and meta.value \
            else meta.value[&"name"]
        result[key] = data_object.get(meta.target_name)
    return result
func _init() -> void:
    var data_object := MyDataClass.new()
    data_object.foo = 0
    data_object.bar = "test"
    var dict := to_dict(data_object)
    print(dict) # { &"json_foo": 0, &"bar": "test" }It's even powerful enough to implement something resembling the builtin  annotated_node.ts extends Node
@meta("rpc", {
    rpc_mode = MultiplayerAPI.RPCMode.RPC_MODE_AUTHORITY,
    transfer_mode = MultiplayerPeer.TransferMode.TRANSFER_MODE_UNRELIABLE,
    call_local = false,
})
func foo() -> void: passmain.ts (root node) extends Node
static func apply_rpc_settings(node: Node) -> void:
    var script = node.get_script() as Script
    if script == null: return
    for meta in script.get_script_meta("rpc"):
        if meta.target_type != Script.MetaTargetType.META_TARGET_FUNCTION:
            push_warning("ignoring @meta(\"rpc\", ...) on non-method")
            continue
        node.rpc_config(meta.target_name, meta.value)
func _init() -> void:
    child_entered_tree.connect(apply_rpc_settings) | 
| I agree the implementation as pure metadata, but I assume that this PR way seems quite indirect. So I feel that this way doesn't differ much from having the script have a reserved word dictionary. As I mentioned in a previous PR #102516, my opinion is that this kind of implementation should be a little more invasive to the core and made readily available in C++/GDExtension and similar contexts. In other words, I think implementing a function like  | 
| I considered  
 Whether or not I agree with that design choice for  That said, I absolutely support something like  | 
| I don't consider this PR a solution to the linked proposal. This syntax is utterly unreadable and I don't even understand why, given that the challenge with custom annotations certainly isn't the parsing part. One of the big technical challenges for custom annotations is, that we don't have a way to resolve conflicts because we don't have namespacing. So if two plugins use the same annotation name we have a problem. This PR doesn't solve this issue. It just pushes the responsibility onto plugin devs. 
 We can't expect plugin devs to figure all this verification out themselves, otherwise implementing annoatations will be a huge mess. It's also basically impossible to design good UX around free form metadata: 
 All of those concerns are totally unrelated to the question of "key value metadata pairs" vs "python decorators". JVM annotations are also only key value pairs. But they are readable, have well defined parameters and can provide good UX. | 
| Fails at readability. That is enough to make it not work. Only way I can see it work if you allow some form of metaprogramming with it but that is also outside scope. Metaprogramming here means something like junit5 annotations where you can put annotations on annotation classes to combine them. Which means custom annotations in the end. | 
| I don't consider a single  Basically, having  | 
| Okay, I'm definitely sensing that this is not the thing people want and this needs to be fleshed out more. That discussion is probably better to have back in the original proposal, godotengine/godot-proposals#1316. | 
This PR implements a new
@metaannotation similar to the one described in #102516 (comment).The
@meta(name: StringName, value: Variant = true)annotation allows script authors to tag classes and class members in their scripts withStringName-keyed arbitrary metadata. This metadata can then be inspected at runtime by calling one of the following newScriptmethods:Array[StringName] Script.get_script_meta_list() const- gets a list of all@metanames appearing in the script.Array[Dictionary] Script.get_script_meta(name: StringName) const- gets a list of dictionaries describing each appearance of@metahaving the givennamein the script.Usage example
annotated_script.gd
introspector_script.gd
Specifications
@metaannotation can target classes, constants, signals, properties, and methods. This includes arbitrarily deep inner classes and their members.@metacan annotate a given target multiple times, even with the samename.@metamust be a constant expression. If omitted, it defaults totrue.Script.get_script_meta(name)returns a list of dictionaries, where each dictionary describes an instance of@meta(name, ...)in the script. The format of the dictionary is described in the docs added in bc46b32. It includes the target name, target type, container class name (if any), and metadata value.Notes
@metais a new builtin annotation and doesn't add support for custom user-defined annotations. However, I believe it should unblock most of the use cases in Allow custom GDScript annotations which can be read at runtime godot-proposals#1316.@metais for constant key-value metadata. The goal was not to implement something like Python decorators in Godot.@metafor GDScript, but support for (e.g.) a companion C#[Meta(...)]attribute hooked up to the same getter interface onScriptshould be feasible.